/*
 * Copyright (C) 2017 WordPlat Open Source Project
 *
 *      https://wordplat.com/InteractiveKLineView/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wordplat.ikvstockchart;


import com.wordplat.ikvstockchart.compat.GestureMoveActionCompat;
import com.wordplat.ikvstockchart.compat.ViewUtils;
import com.wordplat.ikvstockchart.detector.GestureDetector;
import com.wordplat.ikvstockchart.detector.ScaleGestureDetector;
import com.wordplat.ikvstockchart.detector.ViewConfiguration;
import com.wordplat.ikvstockchart.entry.Entry;
import com.wordplat.ikvstockchart.entry.EntrySet;
import com.wordplat.ikvstockchart.render.AbstractRender;
import com.wordplat.ikvstockchart.render.KLineRender;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ScrollHelper;
import ohos.agp.render.Canvas;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.eventhandler.EventHandler;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

/**
 * <p>交互式 K 线图</p>
 * <p>Date: 2017/3/10</p>
 *
 * @author afon
 * @since 2021-05-09
 */
public class InteractiveKLineView extends Component implements Component.DrawTask {
    //static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "AbstractRender");
    private static final boolean DEBUG = true;
    /**
     * 与滚动控制、滑动加载数据相关的属性
     */
    /**
     * dragging 松手之后回中的时间，单位：毫秒
     */
    private static final int OVERSCROLL_DURATION = 500;
    /**
     * dragging 的偏移量大于此值时即是一个有效的滑动加载
     */
    private static final int OVERSCROLL_THRESHOLD = 50;
    /**
     * 空闲
     */
    private static final int KLINE_STATUS_IDLE = 0;
    /**
     * 放手，回弹到 loading 位置
     */
    private static final int KLINE_STATUS_RELEASE_BACK = 2;
    /**
     * 加载中
     */
    private static final int KLINE_STATUS_LOADING = 3;
    /**
     * 加载结束，回弹到初始位置
     */
    private static final int KLINE_STATUS_SPRING_BACK = 4;
    private final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            float f = detector.getScaleFactor();

            if (f < 1.0f) {
                render.zoomOut(detector.getFocusX(), detector.getFocusY());
            } else if (f > 1.0f) {
                render.zoomIn(detector.getFocusX(), detector.getFocusY());
            }
            notifyDataSetChanged();
        }
    });
    /**
     * 控制是否可以进行下拉刷新
     */
    private boolean isRefresh;
    private GestureMoveActionCompat gestureCompat = new GestureMoveActionCompat(null);
    /**
     * 与视图大小相关的属性
     */
    private final RectFloat viewRect;
    private final float viewPadding;

    /**
     * 与数据加载、渲染相关的属性
     */
    private AbstractRender render;
    private EntrySet entrySet;
    private KLineHandler kLineHandler;
    private int kLineStatus = KLINE_STATUS_IDLE;
    private int lastFlingX = 0;
    private int lastScrollDx = 0;
    /**
     * 上一次的 entry 列表大小，用于判断是否成功加载了数据
     */
    private int lastEntrySize = 0;
    /**
     * 上一次高亮的 entry 索引，用于减少回调
     */
    private int lastHighlightIndex = -1;
    private final ScrollHelper scroller;

    /**
     * 与手势控制相关的属性
     */
    private boolean onTouch = false;
    private boolean onLongPress = false;
    private boolean onDoubleFingerPress = false;
    private boolean onVerticalMove = false;
    private boolean onDragging = false;
    private boolean enableLeftRefresh = true;
    private boolean enableRightRefresh = true;
    private static final long INVALIDATE_TIME_INTERVAL = 2000;
    private boolean isEmpty;

    /**
     * InteractiveKLineView
     *
     * @param context
     * @param attrs
     */
    public InteractiveKLineView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * InteractiveKLineView
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public InteractiveKLineView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        viewRect = new RectFloat();
        viewPadding = ViewUtils.vpToPx(context, 10);

        render = new KLineRender(context);

        gestureDetector.setIsLongpressEnabled(true);

        int touchSlop = ViewConfiguration.getScaledTouchSlop();
        gestureCompat.setTouchSlop(touchSlop);

        scroller = new ScrollHelper();

        render.setSizeColor(ViewUtils.getSizeColor(context, attrs, defStyleAttr));

        setListener();
    }

    /**
     * 获取是否可以下拉刷新
     *
     * @return true
     */
    public boolean isRefresh() {
        return isRefresh;
    }

    /**
     * 设置是否可以下拉刷新
     *
     * @param refresh
     */
    public void setRefresh(boolean refresh) {
        isRefresh = refresh;
    }

    public void setEntrySet(EntrySet set) {
        entrySet = set;
    }

    /**
     * notifyDataSetChanged
     */
    public void notifyDataSetChanged() {
        notifyDataSetChanged(true);
    }

    /**
     * notifyDataSetChanged
     *
     * @param invalidate
     */
    public void notifyDataSetChanged(boolean invalidate) {
        render.setViewRect(viewRect);
        render.onViewRect(viewRect);
        render.setEntrySet(entrySet);

        if (invalidate) {
            invalidate();
        }
    }

    /**
     * setRender
     *
     * @param render
     */
    public void setRender(AbstractRender render) {
        render.setSizeColor(this.render.getSizeColor());
        this.render = render;
    }

    public AbstractRender getRender() {
        return render;
    }

    public void setKLineHandler(KLineHandler kLineHandler) {
        this.kLineHandler = kLineHandler;
    }

    public RectFloat getViewRect() {
        return viewRect;
    }

    private final GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {

        @Override
        public void onLongPress(TouchEvent e) {
            if (onTouch) {
                onLongPress = true;
                MmiPoint point = e.getPointerPosition(e.getIndex());
                highlight(point.getX(), point.getY());
            }
        }

        @Override
        public boolean onDoubleTap(TouchEvent e) {
            if (kLineHandler != null) {
                kLineHandler.onDoubleTap(e, e.getPointerPosition(e.getIndex()).getX(), e.getPointerPosition(e.getIndex()).getY());
            }
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(TouchEvent e) {
            if (kLineHandler != null) {
                kLineHandler.onSingleTap(e, e.getPointerPosition(e.getIndex()).getX(), e.getPointerPosition(e.getIndex()).getY());
            }
            return true;
        }

        @Override
        public boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY) {
            if (!onLongPress && !onDoubleFingerPress && !onVerticalMove) {
                if (onDragging && !render.canScroll(distanceX) && render.canDragging(distanceX)) {
                    dragging((int) distanceX);
                } else {
                    scroll((int) distanceX);

                }
                return true;
            } else {
                return false;
            }
        }

        @Override
        public boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX, float velocityY, float scrollY0, float scrollY1) {
            lastFlingX = 0;
            if (!onLongPress && !onDoubleFingerPress && !onVerticalMove && render.canScroll(0)) {


                scroller.doFling(0, 0, (int) -velocityX, 0,
                        Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
                return true;
            } else {
                return false;
            }
        }
    });

    private void highlight(float x, float y) {
        render.onHighlight(x, y - 128);
        invalidate();

        int highlightIndex = render.getEntrySet().getHighlightIndex();
        Entry entry = render.getEntrySet().getHighlightEntry();

        if (entry != null && lastHighlightIndex != highlightIndex) {
            if (kLineHandler != null) {
                kLineHandler.onHighlight(entry, highlightIndex, x, y);
            }
            lastHighlightIndex = highlightIndex;
        }
    }

    private void cancelHighlight() {
        render.onCancelHighlight();
        invalidate();

        if (kLineHandler != null) {
            kLineHandler.onCancelHighlight();
        }
        lastHighlightIndex = -1;
    }

    /**
     * 滚动，这里只会进行水平滚动，到达边界时将不能继续滑动
     *
     * @param dx 变化量
     */
    public void scroll(float dx) {
        render.scroll(dx);
        notifyDataSetChanged();
        /**
         * invalidate();
         */
    }

    /**
     * isEmpty
     *
     * @param isEmpty
     */
    public void isEmpty(boolean isEmpty) {
        this.isEmpty = isEmpty;
    }

    /**
     * 拖动，不同于滚动，当 K 线图到达边界时，依然可以滑动，用来支持加载更多
     *
     * @param dx 变化量
     */
    private void dragging(float dx) {
        if (isEmpty) {
            if (dx < 0) {
                render.updateCurrentTransX(dx);
                render.updateOverScrollOffset(dx);
                notifyDataSetChanged();
                if (Math.abs(dx) > OVERSCROLL_THRESHOLD) {
                    if (enableLeftRefresh && dx > 0) {
                        lastScrollDx = (int) dx - OVERSCROLL_THRESHOLD;
                    }
                    if (enableRightRefresh && dx < 0) {
                        lastScrollDx = (int) dx + OVERSCROLL_THRESHOLD;
                    }
                }
                kLineStatus = KLINE_STATUS_LOADING;
                if (kLineHandler != null) {
                    lastEntrySize = entrySet.getEntryList().size();
                    if (lastScrollDx > 0) {
                        kLineHandler.onRightRefresh();
                    } else if (lastScrollDx < 0) {
                        kLineHandler.onLeftRefresh();
                    }
                } else {
                    refreshComplete();
                }
                notifyDataSetChanged();
            } else if (dx > 0) {
                render.updateCurrentTransX(dx);
                render.updateOverScrollOffset(dx);
                notifyDataSetChanged();
                if (Math.abs(dx) > OVERSCROLL_THRESHOLD) {
                    if (enableLeftRefresh && dx > 0) {
                        lastScrollDx = (int) dx - OVERSCROLL_THRESHOLD;
                    }
                    if (enableRightRefresh && dx < 0) {
                        lastScrollDx = (int) dx + OVERSCROLL_THRESHOLD;
                    }
                }
                kLineStatus = KLINE_STATUS_LOADING;
                if (kLineHandler != null) {
                    lastEntrySize = entrySet.getEntryList().size();
                    if (lastScrollDx > 0) {
                        kLineHandler.onRightRefresh();
                    } else if (lastScrollDx < 0) {
                        kLineHandler.onLeftRefresh();
                    }
                } else {
                    refreshComplete();
                }
                notifyDataSetChanged();
            }
        } else {
            if (render.getMaxScrollOffset() < 0 || dx < 0) {
                render.updateCurrentTransX(dx);
                render.updateOverScrollOffset(dx);
                notifyDataSetChanged();
                if (Math.abs(dx) > OVERSCROLL_THRESHOLD) {
                    if (enableLeftRefresh && dx > 0) {
                        lastScrollDx = (int) dx - OVERSCROLL_THRESHOLD;
                    }
                    if (enableRightRefresh && dx < 0) {
                        lastScrollDx = (int) dx + OVERSCROLL_THRESHOLD;
                    }
                }
                kLineStatus = KLINE_STATUS_LOADING;
                if (kLineHandler != null) {
                    lastEntrySize = entrySet.getEntryList().size();
                    if (lastScrollDx > 0) {
                        kLineHandler.onRightRefresh();
                    } else if (lastScrollDx < 0) {
                        kLineHandler.onLeftRefresh();
                    }
                } else {
                    refreshComplete();
                }
                notifyDataSetChanged();
            }
            /**
             * invalidate();
             */
        }
    }

    /**
     * 更新滚动的距离，用于拖动松手后回中
     *
     * @param dx 变化量
     */
    private void releaseBack(float dx) {
        render.updateCurrentTransX(dx);
        render.updateOverScrollOffset(dx);
        invalidate();
    }

    /**
     * 更新滚动的距离，用于加载数据完成后滚动或者回中
     *
     * @param dx 变化量
     */
    private void springBack(float dx) {
        if (entrySet.getEntryList().size() > lastEntrySize) {
            scroll(dx);
        } else {
            releaseBack(dx);
        }
    }

    /**
     * 加载完成
     */
    public void refreshComplete() {
        refreshComplete(false);
    }

    /**
     * 加载完成
     *
     * @param reverse 是否反转滚动的方向
     */
    public void refreshComplete(boolean reverse) {
        final int overScrollOffset = (int) render.getOverScrollOffset();
        if (overScrollOffset != 0) {
            kLineStatus = KLINE_STATUS_SPRING_BACK;
            lastFlingX = 0;
            scroller.startScroll(0, 0, reverse ? -overScrollOffset : overScrollOffset, 0);
            TaskDispatcher globalTaskDispatcher = mContext.getUITaskDispatcher();
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    notifyDataSetChanged();
                }
            };
            globalTaskDispatcher.delayDispatch(runnable, INVALIDATE_TIME_INTERVAL);
        }
    }

    /**
     * isRefreshing
     *
     * @return boolean
     */
    public boolean isRefreshing() {
        return kLineStatus == KLINE_STATUS_LOADING;
    }

    public void setEnableLeftRefresh(boolean enableLeftRefresh) {
        this.enableLeftRefresh = enableLeftRefresh;
    }

    public void setEnableRightRefresh(boolean enableRightRefresh) {
        this.enableRightRefresh = enableRightRefresh;
    }

    /**
     * isHighlighting
     *
     * @return boolean
     */
    public boolean isHighlighting() {
        return render.isHighlight();
    }

    private void setListener() {
        gestureCompat = new GestureMoveActionCompat(new GestureMoveActionCompat.OnGestureMoveListener() {
            @Override
            public void onHorizontalMove(TouchEvent e, float x, float y) {

            }

            @Override
            public void onVerticalMove(TouchEvent e, float x, float y) {
                final int action = e.getAction();
                final boolean pointerUp = e.getAction() == TouchEvent.OTHER_POINT_UP;
                final int skipIndex = pointerUp ? e.getIndex() : -1;

                float sumX = 0, sumY = 0;
                final int count = e.getPointerCount();
                /**
                 * 把所有还在触摸的手指的位置x，y加起来，后面求平均数，算出中心焦点
                 */
                for (int i = 0; i < count; i++) {
                    if (skipIndex == i) {
                        /**
                         * 跳过非主要指针的抬起动作
                         */
                        continue;
                    }
                    sumY += e.getPointerPosition(i).getY();
                }
                final int div = pointerUp ? count - 1 : count;
                /**
                 * 求平均值，算出中心焦点
                 */
                final float endUpY = sumY / div;

                if (viewOnTouchInterface != null && !onLongPress) {
                    viewOnTouchInterface.touch(action, endUpY);
                }
            }

            @Override
            public void onClick(TouchEvent e, float x, float y) {

            }
        });
        setLayoutRefreshedListener(new LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                viewRect.left = viewPadding;
                viewRect.top = viewPadding;
                viewRect.right = component.getWidth() - viewPadding;
                viewRect.bottom = component.getHeight() - viewPadding;





                if (entrySet == null) {
                    entrySet = new EntrySet();
                }
                notifyDataSetChanged();
            }
        });
        setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                final int action = touchEvent.getAction();
                final boolean pointerUp = touchEvent.getAction() == TouchEvent.OTHER_POINT_UP;
                final int skipIndex = pointerUp ? touchEvent.getIndex() : -1;

                float sumX = 0, sumY = 0;
                final int count = touchEvent.getPointerCount();
                /**
                 * 把所有还在触摸的手指的位置x，y加起来，后面求平均数，算出中心焦点
                 */
                for (int i = 0; i < count; i++) {
                    if (skipIndex == i) {
                        /**
                         * 跳过非主要指针的抬起动作
                         */
                        continue;
                    }
                    sumY += touchEvent.getPointerPosition(i).getY();
                }
                final int div = pointerUp ? count - 1 : count;
                /**
                 * 求平均值，算出中心焦点
                 */
                final float endUpY = sumY / div;
                boolean onHorizontalMove = gestureCompat.onTouchEvent(touchEvent, touchEvent.getPointerPosition(touchEvent.getIndex()).getX(), touchEvent.getPointerPosition(touchEvent.getIndex()).getY());

                onVerticalMove = false;

                gestureDetector.onTouchEvent(touchEvent);
                scaleDetector.onTouchEvent(touchEvent);

                switch (action) {
                    case TouchEvent.PRIMARY_POINT_DOWN: {
                        onTouch = true;
                        onDragging = false;
                        if (viewOnTouchInterface != null) {
                            viewOnTouchInterface.touch(action, endUpY);
                        }
                        break;
                    }
                    case TouchEvent.OTHER_POINT_DOWN: {
                        onDoubleFingerPress = true;
                        break;
                    }
                    case TouchEvent.POINT_MOVE: {
                        onDragging = true;
                        if (onLongPress) {
                            highlight(touchEvent.getPointerPosition(touchEvent.getIndex()).getX(), touchEvent.getPointerPosition(touchEvent.getIndex()).getY());
                        }
                        if (!onHorizontalMove && !onLongPress && !onDoubleFingerPress && gestureCompat.isDragging()) {
                            onTouch = false;
                            onVerticalMove = true;
                        }
                        break;
                    }
                    case TouchEvent.PRIMARY_POINT_UP:
                        if (viewOnTouchInterface != null) {
                            viewOnTouchInterface.touch(action, endUpY);
                        }
                    case TouchEvent.CANCEL: {
                        onLongPress = false;
                        onDoubleFingerPress = false;
                        onTouch = false;
                        onDragging = false;
                        cancelHighlight();
                        break;
                    }
                    default:
                        break;
                }
                return true;
            }
        });
        addDrawTask(this);
        setScrolledListener(new ScrolledListener() {
            @Override
            public void onContentScrolled(Component component, int i, int i1, int i2, int i3) {
                if (onVerticalMove) {
                    return;
                }

                if (scroller.isFinished()) {
                    final int x = scroller.getCurrValue(VERTICAL);
                    final int dx = x - lastFlingX;
                    lastFlingX = x;

                    if (onTouch) {
                        scroller.abortAnimation();
                    } else {
                        if (kLineStatus == KLINE_STATUS_RELEASE_BACK) {
                            releaseBack(dx);
                        } else if (kLineStatus == KLINE_STATUS_SPRING_BACK) {
                            springBack(dx);
                        } else {
                            scroll(dx);
                        }

                        if (render.canScroll(dx) && !scroller.isFinished()) {
                            EventHandler.current().postTask(new Runnable() {
                                @Override
                                public void run() {
                                    invalidate();
                                }
                            }, INVALIDATE_TIME_INTERVAL);
                        }
                    }


                } else {
                    final float overScrollOffset = render.getOverScrollOffset();


                    if (!onTouch && overScrollOffset != 0 && kLineStatus == KLINE_STATUS_IDLE) {
                        lastScrollDx = 0;
                        float dx = overScrollOffset;

                        if (Math.abs(overScrollOffset) > OVERSCROLL_THRESHOLD) {
                            if (enableLeftRefresh && overScrollOffset > 0) {
                                lastScrollDx = (int) overScrollOffset - OVERSCROLL_THRESHOLD;

                                dx = lastScrollDx;
                            }

                            if (enableRightRefresh && overScrollOffset < 0) {
                                lastScrollDx = (int) overScrollOffset + OVERSCROLL_THRESHOLD;

                                dx = lastScrollDx;
                            }
                        }

                        kLineStatus = KLINE_STATUS_RELEASE_BACK;
                        lastFlingX = 0;
                        scroller.startScroll(0, 0, (int) dx, 0);
                        EventHandler.current().postTask(new Runnable() {
                            @Override
                            public void run() {
                                invalidate();
                            }
                        }, INVALIDATE_TIME_INTERVAL);

                    } else if (kLineStatus == KLINE_STATUS_RELEASE_BACK) {
                        kLineStatus = KLINE_STATUS_LOADING;

                        if (kLineHandler != null) {
                            lastEntrySize = entrySet.getEntryList().size();
                            if (lastScrollDx > 0) {
                                kLineHandler.onLeftRefresh();
                            } else if (lastScrollDx < 0) {
                                kLineHandler.onRightRefresh();
                            }
                        } else {
                            refreshComplete();
                        }
                    } else {
                        kLineStatus = KLINE_STATUS_IDLE;
                    }
                }
            }

            @Override
            public void scrolledStageUpdate(Component component, int newStage) {

            }
        });
    }

    private boolean isFirst = true;

    @Override
    public void onDraw(Component component, Canvas canvas) {
        render.render(canvas);
        TaskDispatcher globalTaskDispatcher = mContext.getMainTaskDispatcher();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (isFirst) {
                    postLayout();
                    isFirst = false;
                }
            }
        };
        globalTaskDispatcher.delayDispatch(runnable, 1000);
    }

    public ViewOnTouchInterface viewOnTouchInterface;

    /**
     * 定义回调方法
     *
     * @param viewOnTouchInterface
     */
    public void setOnTouchInterface(ViewOnTouchInterface viewOnTouchInterface) {
        this.viewOnTouchInterface = viewOnTouchInterface;
    }

    /**
     * ViewOnTouchInterface
     */
    public interface ViewOnTouchInterface {
        /**
         * touch
         *
         * @param getAction
         * @param endUpY
         * @return boolean
         */
        boolean touch(int getAction, float endUpY);
    }
}
